home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-03 / softip.zip / ISSUE1.STP < prev    next >
Text File  |  1990-10-21  |  85KB  |  1,900 lines

  1. (C)1990 Marquis Computing Inc., All rights reserved.
  2. BASIC Softips(tm)
  3. Entire contents copyrighted, 1990
  4. Issue number 1
  5. 10/20/1990
  6. 5
  7. TOC
  8. TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TO
  9.  
  10. Welcome to BASIC Softips for November 1990. Thank you downloading it and good
  11. reading! Use the up/down arrows, pageup/pagedown, control pageup/pagedown
  12. and home and end keys to navigate through an article or section. Press the
  13. ALT key or click the mouse on the top line of the screen for a menu.
  14.  
  15.   √ Inside this issue get three FREE working programs!
  16.   √ Tips to make your FOR...NEXT loops as much as 80% faster!
  17.   √ Build a working, learning expert system
  18.   √ Learn how to read Binary Coded Decimal
  19.   √ Save 14K off an executable using PDS
  20.   √ Explore the File Allocation Table & how DOS erases a file
  21.   √ Use DOS interrupts to make your programs more powerful
  22.  
  23. This months segments include:
  24.  
  25.   ■ FORUM - introduction to BASIC Softips...why the author is C-ing red!
  26.   ■ Q&A - a forum to answer your questions...How to make a program
  27.     run faster.
  28.   ■ PROJECT OF THE MONTH - a multi-purpose input routine with commented
  29.     source code that can save you over 14K off the size of an EXE!
  30.   ■ LONG TERM PROJECT - a working expert system, part one...Just what is
  31.     an expert system anyway? With source code for a working, learning
  32.     expert system!
  33.   ■ The BASICS - how dos manages disk drives...File Allocation Tables,
  34.     FAT chains, DOS efficiency, erased files and more.
  35.   ■ ADVANCED BASIC - part one...using interrupts to explore DOS. Includes
  36.     source code for program to read and display DOS boot sector & FAT using
  37.     BASIC.
  38.   ■ BOOK OF THE MONTH - A review of ADVANCED MSDOS The Microsoft guide
  39.     for Assembly Language and C programmers.
  40.   ■ SOFTWARE OF THE MONTH - A hands on review of A.J.S. Publishings dBASE
  41.     file support library, db/LIB.
  42. END TOC
  43. ADD 1
  44. Your add could be here! Very, very competitive rates. Reach you're marketplace
  45. using our unique, free and widely distributed computer based magazine.
  46. Rates are as low as $5.00 per add! Contact Hank at 1.201.707.1316 for more
  47. information. Call now!
  48.                        Can you afford not to?
  49. END ADD 1
  50. ADD 2
  51. This computer based magazine is free to over 400,000 thousand Bulletin Board
  52. subscribers! As we are able to document downloads, our readership will
  53. be determined, and then our add rates. But never fear! Our rates will always
  54. be the best in any computer magazine!
  55. BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC
  56. END ADD 2
  57. ADD 3
  58. Marquis Computing Inc., Call 1.201.707.1316 and ask for Hank
  59.          ■ Custom software programming
  60.          ■ DOS, dBASE and file utilities
  61.          ■ Training and education
  62.          ■ Technical writing & editing
  63. END ADD 3
  64. ADD 4
  65. To Subscribe to BASIC Softips for one year, send $12.00 with address to:
  66. Marquis Computing, 135 Chestnut Street, Bridgewater NJ 08807. You will
  67. receive your BASIC Softips on floppy diskette every month. Save downloading
  68. charges! Subscribe today. With each issue get the latest READER too!
  69. BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC
  70. END ADD 4
  71. ADD 5
  72. Do you want to write an article, author or sponsor a segment of BASIC Softips?
  73. We are actively soliciting articles on topics of programming and computers.
  74. Contact Marquis Computing, 135 Chestnut Street, Bridgewater NJ 08807. Or via
  75. CompuServe at 76120,2413. Heres your chance!
  76. END ADD 5
  77. Q&A Q&A
  78. Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A
  79.  
  80. The Q&A section is for any questions you have regarding anything, except
  81. maybe the meaning of life.
  82.  
  83. Q: What can I do to optimize a program for speed?
  84.  
  85. A: We all want to know how to make our programs faster. There is no inherent
  86.    reason a BASIC program should run slower than any other program. Often
  87.    the reason our programs run slow is HOW we write them, not what language
  88.    we use. Look at the example below. A simple FOR...NEXT loop to search
  89.    through an array searching for a string.
  90.  
  91.    FOR X# = 1 TO UBOUND(Array$) STEP 1
  92.       Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
  93.       IF INSTR(UCASE$(Array$), Search$) > 0 THEN
  94.         Element = X#
  95.         EXIT FOR
  96.       END IF
  97.    NEXT X#
  98.  
  99.    Running this loop on my machine (a 80386 running with it's clock speed
  100.    slowed to 10MHZ) this loop averaged .69 seconds over 10 runs. Then I
  101.    began optimizing it. First, by moving the Search$ = UCASE$... line
  102.    outside of the loop. This line is always the same, so why leave it
  103.    inside the loop where we lose speed performing it each time? The code
  104.    below averaged .46 seconds over 10 runs. A savings of an 33%! Move all
  105.    non variant code to OUTSIDE of the loop.
  106.  
  107.    Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
  108.    FOR X# = 1 TO UBOUND(Array$) STEP 1
  109.       IF INSTR(UCASE$(Array$), Search$) > 0 THEN
  110.         Element = X#
  111.         EXIT FOR
  112.       END IF
  113.    NEXT X#
  114.  
  115.    Then I added a simple line, DEFINT A-Z, and changed the loop variable
  116.    X# to X. The code below ran in an amazing .08 seconds a whopping 83%
  117.    reduction in speed from the last change! What a difference changing the
  118.    loop variable made! Integers are the 'natural' numbers of BASIC - use
  119.    DEFINT A-Z at the top of each function, sub and module. And always use
  120.    an integer loop variable, if you can.
  121.  
  122.    DEFINT A-Z
  123.  
  124.    Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
  125.    FOR X = 1 TO UBOUND(Array$) STEP 1
  126.       IF INSTR(UCASE$(Array$), Search$) > 0 THEN
  127.         Element = X
  128.         EXIT FOR
  129.       END IF
  130.    NEXT X
  131.  
  132.    Next, I moved the UBOUND(Array$) out, as shown below. Interestingly
  133.    though, there was NO perceptible change in loop speed.
  134.  
  135.    DEFINT A-Z
  136.  
  137.    Count = UBOUND(Array$)
  138.    Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
  139.  
  140.    FOR X = 1 TO Count STEP 1
  141.       IF INSTR(UCASE$(Array$), Search$) > 0 THEN
  142.         Element = X
  143.         EXIT FOR
  144.       END IF
  145.    NEXT X
  146.  
  147.    I removed the STEP 1 line, as we are incrementing by one, and that is
  148.    what BASIC FOR loops do by default. This brought about a .01 second
  149.    reduction in run time. Finally, I removed the X from the NEXT X line.
  150.    This brought about and additional .01 second reduction. I ended up
  151.    with the code shown below. It runs in an average of.06 seconds. All
  152.    the changed I made resulted in a loop running an incredible 82%
  153.    decrease in execution time.
  154.  
  155.    DEFINT A-Z
  156.  
  157.    Count = UBOUND(Array$)
  158.    Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
  159.  
  160.    FOR X = 1 TO Count
  161.       IF INSTR(UCASE$(Array$), Search$) > 0 THEN
  162.         Element = X
  163.         EXIT FOR
  164.       END IF
  165.    NEXT
  166.  
  167.    So, often it's the WAY we program, not WHAT we program in that gives us
  168.    slow programs. Imagine a whole application full of such loops or similar
  169.    WHILE...WEND, DO...LOOP functions. Take a close look at all those loops
  170.    to see if you can get some of these savings.
  171.  
  172.  
  173. If any of you have similar experiments or questions, please send them in!
  174.  
  175. Please submit any questions, problems, corrections or
  176. comments to :
  177.  
  178.         Electronic (preferred):
  179.         Editor
  180.         BASIC Softips
  181.         CompuServe 76120, 2413
  182.  
  183.         Paper (if you must):
  184.         Editor
  185.         BASIC Softips
  186.         135 Chestnut Street
  187.         Bridgewater NJ 08807
  188.  
  189.  
  190. END Q&A Q&A
  191. FORUM FORUM
  192. FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM
  193.  
  194.  C-ing red!
  195.  
  196.  I am writing this newsletter because I feel slighted. Everywhere I look,
  197.  all I see is 'C'! People are always telling me (including on occasion MS
  198.  tech support) that certain things just can't be done in BASIC. Well, to me,
  199.  That's an open challenge! The whole concept of this magazine is to dispel
  200.  the vicious rumors that BASIC is a toy. To that end, over the next series
  201.  of these newsletters, we are going to blow the doors off of ANYONE who
  202.  thinks BASIC is a toy!
  203.  
  204.  We will create a hyper text on-line help program, a multipurpose text entry
  205.  routine, a natural language interpreter, a file unerase utility-our own
  206.  version of NORTON utilities, a fully functional expert system, a database
  207.  engine capable of reading and writing dBASE and much, much more.
  208.  
  209.  We will also cover, for newcomers, basic programming concepts such as
  210.  modules, structured programming, functions, making full use of the world of
  211.  DOS interrupts. You CAN do anything in BASIC that they can do in 'C'!
  212.  
  213.  We will also explore the world of BASIC libraries available to us - AJS
  214.  Publishings dB/LIB, an excellent dBASE library. (By the way, how come no
  215.  dBASE magazine ever mentions the fact that there is a real alternative to
  216.  developing in dBASE or CLIPPER? I have written dBASE applications in
  217.  guess what - BASIC!) We will also examine Crescent softwares fine general
  218.  routine library QuickPac Professional and others.
  219.  
  220.  My goal is nothing less than to make BASIC programmers stand up, and say
  221.  proudly "Yes, we used MS BASIC to develop that application." You see BASIC
  222.  is not a toy anymore. Recent revisions to BASIC make it on par with virtually
  223.  ant other language. The Professional Development Systems' inclusion on
  224.  overlay support, plus stub-files and using BASICs built-in runtime module
  225.  support allow you to create any application you can dream up. And in this
  226.  newsletter you will see how to do it!
  227.  
  228.  Source Code you ask? Yes and lots of it. Example programs? Yes and lots of
  229.  them. The whole goal of this magazine is faster, smaller, more full featured
  230.  and more powerful programs - all using BASIC.
  231.  
  232.  Your comments?
  233. END FORUM FORUM
  234. PROJECT OF THE MONTH PROJECT OF THE MONTH
  235. PROJECT OF THE MONTH PROJECT OF THE MONTH PROJECT OF THE MONTH PROJECT OF THE
  236.  
  237. This months project is a multiple purpose text entry routine, written
  238. entirely in BASIC. If will operate under QB or the PDS.
  239.  
  240. In general, this program allows you more flexibility than using BASICs
  241. line input function. Although BASICs line input function offers full
  242. string editing support, you can only exit it with a carriage return.
  243. This program offers full editing support, but YOU define how to exit
  244. the routine. In addition, you can specify if the input will be character
  245. or numeric, if the string returned will be upper case or lower case and
  246. the color of the input. In the future we are going to expand this routine
  247. to give it a mask. This mask will let you return formatted strings from
  248. the user, for example Social Security numbers, phone numbers and more.
  249.  
  250. As an added benefit, using this routine, and linking out full editing
  251. support using the PDS stub file NOEDIT.OBJ results in a savings of
  252. over 14,000 bytes of the size of a compiled executable! More powerful
  253. and smaller. That's what it's all about!
  254.  
  255. Use the Cut segment command from the main utilities menu to save this
  256. file to disk. Give it a name like EDITOR1.BAS so you can keep them
  257. straight each month as we add features. When you load this into BASIC,
  258. delete all of the text lines above. The program starts immediately below.
  259.  
  260.  'Start of program-------------------------------------------------------
  261.  '
  262.  '(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
  263.  'You may use this program for anything or any purpose including inclusion
  264.  'into programs you write BUT you cannot sell this source code. Written by
  265.  'Hank Marquis. revised 9/8/90.
  266.  
  267. DEFINT A-Z
  268. DECLARE SUB LineInput (Msg$, code$, Caps, Num, row, Col, Fore, back)
  269.  
  270.  'you program code here
  271.  '  .
  272.  '  .
  273.  
  274. Msg$ = "Enter your name:"    'prompt string
  275. Code$ = CHR$(27) + CHR$(13)  'exit on escape or enter
  276. Caps = 0                     'do not force to capitals
  277. Num = 0                      'do not force numeric input
  278. Row = 5                      'print prompt on row 5
  279. Col = 5                      'start printing ar column 5
  280. fore = 7                     'use white for foreground
  281. back = 0                     'use black as background
  282.  
  283. LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back  'make the call
  284.  
  285.  '  .
  286.  '  .
  287.  'process Msg$ returned from user here
  288.  '
  289.  
  290.  
  291. END
  292.  
  293. DEFINT A-Z
  294. SUB LineInput (Msg$, code$, Caps, Num, row, Col, Fore, back)
  295.  
  296.   'This sub routine is used to write a prompt string, and then accept
  297.   ' input. By setting variables which are defined below you have
  298.   ' control over where the editing happens and how.
  299.   '
  300.   'All standard editing keys are supported :
  301.   '  arrow left, arrow right = move one character left or right
  302.   '  control arrow left, arrow right = move one word left or right
  303.   '  home = jump to beginning of line
  304.   '  end = jump to end of line
  305.   '  backspace = delete the character to the left of the cursor
  306.   '  delete = remove the character at the cursor
  307.   '
  308.   'You may choose if the user will enter numbers or letters or
  309.   ' if the input string will be capitalized. By setting Msg$ to "",
  310.   ' no prompt string is presented, allowing use of LineInput to get
  311.   ' single characters.
  312.   '
  313.   '
  314.   'Msg$  = on input the prompt message to be displayed
  315.   '      = on exit the user entered string
  316.   '
  317.   'Code$ = a concatenated string containing the key strokes to exit the
  318.   '        sub routine. Example :
  319.   '        Code$ = CHR$(13) + CHR$(27)  'sets exit code to the escape key or
  320.   '                                     ' the enter key
  321.   '
  322.   'Caps  = force capitalization flag. When Caps=1 the string is forced to
  323.   '        all capitals
  324.   '
  325.   'Num   = force numeric input flag. When Num=-1 the string is forced to
  326.   '        be a numeric input. When Num is > 0, Num characters are input
  327.   '        and then the routine is exited. Use this is determine the
  328.   '        quantity of characters that a user can input
  329.   '
  330.   'Row   = Row to position prompt string on
  331.   'Col   = Column to position prompt string at
  332.   '
  333.   '
  334.   'here are some examples of using line input
  335.   '
  336.   ' example #1 : print a prompt string and enter a string
  337.   '
  338.   ' Msg$ = "Enter your name:"    'prompt string
  339.   ' Code$ = CHR$(27) + CHR$(13)  'exit on escape or enter
  340.   ' Caps = 0                     'do not force to capitals
  341.   ' Num = 0                      'do not force numeric input
  342.   ' Row = 5                      'print prompt on row 5
  343.   ' Col = 5                      'start printing ar column 5
  344.   ' fore = 7                     'use white for foreground
  345.   ' back = 0                     'use black as background
  346.   ' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
  347.   '
  348.   ' example #2 : print a prompt string and a return a number
  349.   '
  350.   ' Msg$ = "Enter birthday:"     'prompt string
  351.   ' Code$ = CHR$(27) + CHR$(13)  'exit on escape or enter
  352.   ' Caps = 0                     'do not force to capitals
  353.   ' Num = -1                     'force numeric input
  354.   ' Row = 5                      'print prompt on row 5
  355.   ' Col = 5                      'start printing ar column 5
  356.   ' fore = 7                     'use white for foreground
  357.   ' back = 0                     'use black as background
  358.   ' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
  359.   '
  360.   ' example #3 : print a prompt and a return a string 4 characters long
  361.   '
  362.   ' Msg$ = "Enter address:"      'prompt string
  363.   ' Code$ = CHR$(27) + CHR$(13)  'exit on escape or enter
  364.   ' Caps = 0                     'do not force to capitals
  365.   ' Num = -1                     'force numeric input
  366.   ' Row = 5                      'print prompt on row 5
  367.   ' Col = 5                      'start printing ar column 5
  368.   ' fore = 7                     'use white for foreground
  369.   ' back = 0                     'use black as background
  370.   ' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
  371.   '
  372.   ' example #4 : suppress prompt and a return a single capital character
  373.   '
  374.   ' Msg$ = ""                    'prompt string
  375.   ' Code$ = ""                   'exit on any key
  376.   ' Caps = 0                     'do not force to capitals
  377.   ' Num = 1                      'exit on one character
  378.   ' Row = 5                      'print prompt on row 5
  379.   ' Col = 5                      'start printing ar column 5
  380.   ' fore = 7                     'use white for foreground
  381.   ' back = 0                     'use black as background
  382.   ' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
  383.   '
  384.   '
  385.   'Some things you could add are...
  386.   '1) you could add a mask to the call like this:
  387.   '  LineInput (Mask$, Msg$, Code$, Caps, Num, Row, Col, Fore, Back)
  388.   '  where mask is a template like ...-...-... for phones or
  389.   '  ...-..-.... for Social Security numbers etc.. To do this you would
  390.   '  need to add some code in the section where the string is printed - but
  391.   '  don't forget to add some code in the editing sections to jump over the
  392.   '  mask!
  393.   '
  394.   '2) You could have BASIC format the numeric string input by using the
  395.   '  PRINT USING command. You could also put this in the printing section
  396.   '  like this maybe..
  397.   '
  398.   '  PrintString$ = ""
  399.   '  FOR X = 1 TO LEN(text$)
  400.   '   PrintString$ = PrintString$ + "#"
  401.   '   IF X MOD 3 = 0 THEN PrintString$ = PrintString$ + ","
  402.   '  NEXT
  403.   '  PRINT USING PrintString; VAL(text$)
  404.   '
  405.   '-----------------------------------------------------------------------
  406.  
  407.   'The main sub routine starts here.
  408.   ' here we set up our variables and get ready to edit a string
  409.  
  410.   COLOR Fore, back               'set the colors
  411.   Msg$ = LTRIM$(RTRIM$(Msg$))    'use LRTRIM to remove any blanks from the
  412.                                  ' left of the message string
  413.  
  414.   blank$ = SPACE$(1)             'make a blank line for use later on
  415.  
  416.   LOCATE row, Col, 1, 0, 7       'position the prompt string
  417.   PRINT Msg$;                    'print the prompt string message
  418.  
  419.   CurCol = LEN(Msg$) + 6         'set our position markers
  420.   Col = CurCol                   ' "   "      "       "
  421.  
  422.   StartScan = 7                  'set for "insert cursor"
  423.   StopScan = 7                   'set for "insert cursor"
  424.  
  425.   ValidNums$ = "0123456789"      'build a string of valid numbers which
  426.                                  ' can be entered
  427.  
  428.   '------------
  429.   'here we do the actual editing of the string
  430.   'this do...loop is used until a key in the Code$ string is entered,
  431.   ' then the loop is exited.
  432.   DO
  433.     Ky$ = INKEY$                'get a key from BASIC
  434.     IF LEN(Ky$) > 0 THEN        'the user hit a key
  435.  
  436.       IF Caps THEN Ky$ = UCASE$(Ky$) 'set to caps if Caps flag set to 1
  437.       'Below, we make the value negative for extended key strokes
  438.       ' but you could make them something else. Here we convert the
  439.       ' key into ASC code. This is needed because BASIC returns
  440.       ' a keystroke of 1 or 2 characters. This is an easy way to process
  441.       ' extended keys like arrows, pageup, function keys and ALT & CTRL
  442.       ' keys.
  443.  
  444.       IF LEN(Ky$) > 1 THEN      'this is an extended key code
  445.         Ky = ASC(RIGHT$(Ky$, 1)) * -1
  446.       ELSEIF LEN(Ky$) = 1 THEN  'this is a normal key
  447.         Ky = ASC(RIGHT$(Ky$, 1))
  448.       END IF 'end of...converting INKEY to ASC()
  449.  
  450.       IF Ky > 0 AND Num = -1 THEN
  451.         IF INSTR(code$, CHR$(Ky)) = 0 AND INSTR(ValidNums$, Ky$) = 0 THEN
  452.         'if this key isn't an exit key AND
  453.         'we are checking for numbers AND
  454.         'if this is NOT a number THEN
  455.           BEEP                            'user did not enter a number
  456.           Ky = 255                        'set Ky to null so nothing happens
  457.         END IF 'end of...not a number
  458.       END IF   'end of...checking for numbers
  459.  
  460.  
  461.      'This select case block is where we determine the type of key
  462.      ' entered, and then process it accordingly.
  463.  
  464.      SELECT CASE Ky
  465.  
  466.         CASE 8 'back space
  467.           'Back space removes the character to the LEFT of the
  468.           ' cursor. Delete removes the current character.
  469.           IF LEN(LTRIM$(RTRIM$(text$))) THEN
  470.             CurCol = CurCol - 1
  471.             F$ = LEFT$(text$, (CurCol - Col))
  472.             L$ = RIGHT$(text$, LEN(text$) - (CurCol - Col) - 1)
  473.             text$ = F$ + L$
  474.             changed = 1
  475.           END IF 'if procesable text$
  476.  
  477.         CASE 32 TO 125                  ' the basic keyboard alphabet
  478.           'If we are inserting then break the sting into two sub
  479.           ' strings called F$ - first and L$ - last. We break the string
  480.           ' right at the current cursor position. Then, if we are inserting
  481.           ' we add the new character Ky$ to the front off L$.
  482.           ' for example :
  483.           '  This is+sample string. (note the location of the cursor)
  484.           '  F$ = This is
  485.           '  L$ = sample string
  486.           '  Ky$ = a
  487.           '  L$ = "a" + "sample string" or "a sample string"
  488.           ' Then we combine the parts,
  489.           '  This is a sample string.
  490.            IF Inserting THEN
  491.             F$ = LEFT$(text$, (CurCol - Col))
  492.             L$ = Ky$ + RIGHT$(text$, LEN(text$) - (CurCol - Col))
  493.             text$ = F$ + L$
  494.             CurCol = CurCol + 1
  495.             changed = 1                   'set the changed flag to print it
  496.           ELSE                            'we are overstriking
  497.             text$ = text$ + SPACE$(1)     'make room for the next character
  498.             CurCol = CurCol + 1           'bump up the position in the string
  499.             MID$(text$, CurCol - Col) = Ky$ 'set the string to the character
  500.             changed = 1                   'set the changed flag to print it
  501.           END IF 'insert/overstrike options
  502.  
  503.         CASE -71 'home
  504.           CurCol = Col
  505.           LOCATE row, CurCol, 1, StartScan, StopScan
  506.  
  507.         CASE -75 'left arrow
  508.           CurCol = POS(0) - 1
  509.            IF CurCol < Col THEN CurCol = LEN(text$) + Col
  510.           LOCATE row, CurCol, 1, StartScan, StopScan
  511.  
  512.         CASE -77 'right arrow
  513.           'POS(0) returns from BASIC the current horizontal cursor position
  514.           ' we add 1 to it to move the cursor position 1 to the right.
  515.           ' Using LOCATE positions the cursor. If we had used the Changed
  516.           ' variable to re-print the line, it would result in some
  517.           ' flickering of the screen.
  518.           CurCol = POS(0) + 1
  519.            IF CurCol > LEN(text$) + Col THEN CurCol = Col
  520.           LOCATE row, CurCol, 1, StartScan, StopScan
  521.  
  522.         CASE -79 'end
  523.           'The end of the editing box is the location of the start of
  524.           ' the text string + the length of the text string$
  525.           CurCol = LEN(text$) + Col
  526.           LOCATE row, CurCol, 1, StartScan, StopScan
  527.  
  528.         CASE -83 'delete
  529.           'Delete is similar to backspace but also different. Delete
  530.           ' pulls the rest of the string toward the left as it removes
  531.           ' the character at the cursor.
  532.  
  533.           textwid = LEN(LTRIM$(RTRIM$(text$))) 'get length of text string
  534.  
  535.           IF textwid AND LEN(text$) - (CurCol - Col) - 2 > -1 THEN
  536.             'if there is a character to the right of the cursor is indicated
  537.             '  by LEN(text$) - (CurCol - Col) - 2 > -1
  538.             CurCol = CurCol - 1   'move cursor to the left 1 space
  539.             F$ = LEFT$(text$, (CurCol - Col) + 1)  'break the string in half
  540.             'note that below, we take LEN(text$)...-2. The -2 is to
  541.             ' get rid of the character at the cursor PLUS pull the
  542.             ' end of the rest of L$ to the left.
  543.             L$ = RIGHT$(text$, LEN(text$) - (CurCol - Col) - 2)
  544.             CurCol = CurCol + 1 'move the cursor to the new character
  545.             text$ = F$ + L$     're-combine the string
  546.             changed = 1         'set to re-print the string
  547.  
  548.           ELSE   'nothing to delete!
  549.             BEEP
  550.           END IF 'if processable text$
  551.  
  552.         CASE -82 'insert
  553.           'This is a toggle between inserting and overstriking.
  554.           IF Inserting = 0 THEN
  555.            Inserting = 1
  556.            StartScan = 0  'this puts the cursor marker on the bottom line
  557.           ELSE
  558.            Inserting = 0
  559.            StartScan = 7  'this puts the cursor marker on the top line
  560.           END IF
  561.           changed = 1
  562.  
  563.         CASE -115 'ctrl left
  564.           'start at the current cursor position and count BACK until the
  565.           '  first space is found, then set the position of the space
  566.           '  as the new current cursor position. Setting Changed = 1
  567.           '  forces a re-print of the string, hence updating the display
  568.           FOR X = CurCol - 1 TO Col + 1 STEP -1
  569.            IF MID$(text$, X - Col, 1) = CHR$(32) THEN
  570.              CurCol = X
  571.              changed = 1
  572.              EXIT FOR
  573.            END IF
  574.           NEXT
  575.  
  576.         CASE -116 'ctrl right
  577.           'start at the current cursor position and count UP until the
  578.           '  first space is found, then set the position of the space
  579.           '  as the new current cursor position. Setting Changed = 1
  580.           '  forces a re-print of the string, hence updating the display
  581.           FOR X = (CurCol - Col) + 1 TO (LEN(text$) + Col)
  582.            IF MID$(text$, X, 1) = CHR$(32) THEN
  583.              CurCol = X + Col
  584.              changed = 1
  585.              EXIT FOR
  586.            END IF
  587.           NEXT
  588.  
  589.       END SELECT
  590.       IF changed THEN
  591.         'Changed is used to indicate that we made some change in the string
  592.         ' or the cursor position. If Changed = 1 then we want to re-print
  593.         ' the string.
  594.         changed = 0               'reset changed flag to 0
  595.  
  596.         LOCATE row, Col, 0        'locate where the text Starts
  597.         PRINT blank$;             'print a blank line to erase present line
  598.  
  599.         LOCATE row, Col, 0        'locate where the text Starts
  600.         PRINT text$;              'print the text line
  601.         blank$ = SPACE$(LEN(text$)) 'reset the blank line for next time
  602.         LOCATE row, CurCol, 1, StartScan, StopScan 'position the cursor
  603.       END IF 'end of ...if changed
  604.  
  605.     END IF 'end of ...user hit a key structure
  606.  
  607.     IF Ky > 0 THEN
  608.       IF Num > 0 AND LEN(text$) >= Num OR INSTR(Code$, CHR$(Ky)) > 0 THEN
  609.          'if we have entered Num characters OR
  610.          ' if an exit key was pressed
  611.          ' then exit the loop
  612.          EXIT DO
  613.       END IF 'end of...if user hit exit key or length of string limit
  614.     END IF   'end of...if this is a normal key
  615.  
  616.   LOOP
  617.  
  618.   '------------
  619.   'this is the end of the subroutine
  620.  
  621.   Msg$ = LTRIM$(RTRIM$(text$))   'remove any leading or trailing blanks
  622.  
  623.   code$ = Ky$                    'set the Code$ variable to the last key
  624.                                  ' entered so that you can tell what key
  625.                                  ' the user selected to exit the routine.
  626.  
  627.                                  'This is handy when you give the user
  628.                                  ' a choice of two inputs and you want to
  629.                                  ' know which one was selected.
  630.  
  631. END SUB
  632.  
  633.  
  634. END PROJECT OF THE MONTH PROJECT OF THE MONTH
  635.  
  636. LONG TERM LONG TERM
  637. LONG TERM PROJECT LONG TERM PROJECT LONG TERM PROJECT LONG TERM PROJECT LONG T
  638.  
  639. Over the next few issues we will create a working, full featured expert
  640. system. Our expert will be able to do anything we want it to. Given
  641. a few examples it will be able to astound and amaze you and your friends.
  642. Then you can start adding artificial intelligence to your programs - and
  643. astound your users!
  644. The expert we will build will be of a type known as a forward chaining,
  645. parallel system. It will be a neural network. After we program it, we
  646. will give it some examples, and it will actually learn from its mistakes
  647. and get better and better as you use it. This wont be a "toy" either. Once
  648. we get done with it, I'll show you how to build in hooks for dBASE or Lotus
  649. so that you can give your expert applications real teeth! Sound interesting?
  650. Lets go! But first we must cover some basics of expert systems. The following
  651. article covers the basic operations and acronyms of what we are going to be
  652. doing.
  653.  
  654. Just what is an Expert System?
  655.  
  656. An expert system is a program which mimics a human expert in a given field.
  657. Expert systems usually focus in a narrow area of expertise. Their area of
  658. knowledge is known as a Domain Of Enquiry. Specialized knowledge is captured
  659. and stored in a Knowledge Base. In our expert, knowledge will be entered by
  660. you as you build it. Most expert systems consists of stated Rules. A rule is
  661. the chain of events or actions which will induce the expert to come to some
  662. conclusion. In our expert, the rules will be stored in an array. The
  663. Knowledge Base may shape these rules, allowing the expert system to make
  664. deductions. These rules are typically not variable, except by a programming
  665. effort referred to as Knowledge Engineering. Artificial Intelligence (AI)
  666. refers to the entire field of study - like physics. An expert system applies
  667. rules to its Knowledge Base and provides expert opinions. Well designed
  668. expert systems give performance equal to or better than their human
  669. counterparts. They do so in a manner which is easy, fast, inexpensive and
  670. consistent. Well designed expert systems are also easy to use. They require
  671. no specialized actions on the part of the user. They allow users the benefit
  672. of not having to be an expert in the field that the expert is. Better still,
  673. some even give an explanation as to why and how it came to a conclusion.
  674.  
  675. How do expert systems get to be an expert?
  676.  
  677. Most AI packages are "shells". A shell is a basic system which the user must
  678. program with both it's expertise and user interface. The user designs the
  679. layout of the system and then programs in the knowledge. Expert systems are
  680. provided with their knowledge. This knowledge is stored in rules. Most expert
  681. systems follow a rules based approach. In these systems the conclusions are
  682. predefined in a series of nested rules statements.  For example :
  683.  
  684. Rule 1:
  685. IF    : symptom is intermittent bad data
  686.     AND : facility is analog
  687.     AND : protocol is ASCII
  688.     AND : application is dial-up modem
  689. THEN  : problem is line noise hits
  690.  
  691. That looks a lot like BASIC and it is similar. Should the above rule prove
  692. faulty with usage, it is changed. The Knowledge Engineer is the programmer
  693. who would make the changes to the rules or modify the shell. Most expert
  694. system shell systems have no method of directly modifying their own rules. If
  695. there was a method for a program to modify itself, it could do so without
  696. requiring the expense and time of a knowledge engineer. While, in fact, self
  697. learning systems are somewhat rare, they do exist. A learning expert system
  698. has a built in mechanism to modify its own rules based on its performance
  699. feedback. That is the expert system can modify the manner it uses based on
  700. wether the experts operation was right or wrong. If the expert was wrong, it
  701. could modify the way it came to its deduction to reflect the real or desired
  702. outcome it could learn from experience. A neural network is an expert system
  703. which learns by example. You give it an outcome, along with the inputs which
  704. should give that outcome, and the expert builds its own rules. It mimics the
  705. actual process of the human mind - building its own pathways of connectivity.
  706. By repetitively showing the neural network examples of the desired result,
  707. the expert learns from experience. This is the process which our expert will
  708. follow, learning by experience.
  709.  
  710. What is a good application for an expert system?
  711.  
  712. Good applications for an expert system are any operations which are
  713. essentially a matching of multiple inputs to certain outputs or operations.
  714. Obviously, the human expert operates this way. The human expert observes the
  715. symptoms, the application or operation and then, based on experience,
  716. deduces the most likely cause. And that is the most important aspect.
  717. Human experts operate based on experience. Who would deny that a more
  718. experienced technician or professional makes better, more accurate decision
  719. than an inexperienced one? Likewise, for an expert system to be reliable,
  720. there has to be some history of past occurrences. Expert systems do not
  721. produce original thought. They simply relate one or more given conditions to
  722. a given result. Virtually any operation where there are certain associated
  723. inputs which typically result in the same output are good applications for
  724. an expert system. Some past expert systems have included:
  725.  
  726. MYCIN -  prescribing mediations,
  727. PROSPECTOR - determining chance of finding precious metals,
  728. DENDRAL - determining chemical structures and
  729. PUFF - breathing disorders.
  730.  
  731. Notice how they all focus in some well defined area. Each also mimics an
  732. expert which is able to deduce the outcome based on asking some questions or
  733. observing some result. For example, PUFF, the expert system on breathing
  734. disorders mimics a medical specialist in respiratory ailments. Observations
  735. the Doctor might make are :
  736.         This is a female.
  737.         She is somewhat pale.
  738.         She is complaining of shortness of breath.
  739. Based on these observations, questions the Doctor might ask are:
  740.         Does your chest feel tight?
  741.         Does you family have a history of asthma?
  742. These observations, in conjunction with the questions asked, gather data and
  743. build relationships which make an expert opinion, in this case a medical
  744. diagnosis. Furthermore, this process could be for any field. The more complex
  745. the item in question the larger the possible problems. The more complex the
  746. field, the longer it takes to learn the reasoning process. Doctors go to
  747. school for years to learn the Knowledge base of medical data then they learn
  748. by applying that knowledge base in real situations. So good applications for
  749. an expert system are those which:
  750.  
  751. 1) Have an outcome which is deducible based on some inputs,
  752. 2) Have many variables and many possible results making for very complex
  753.    deductions,
  754. 3) Have many relationships between and among the input variables and
  755.    outputs,
  756. 4) Require years of study or experience to master,
  757. 5) Have fairly stable and consistent inputs or symptoms,
  758. 6) Posses an existent base of knowledge upon which to learn from and
  759. 7) Be an application valuable enough to warrant the benefits of an expert
  760.    system.
  761.  
  762. Ok. Enough about what an expert is, how do they work? Well, essentially, all
  763. the expert does is match patterns. Our expert will take a various amount of
  764. inputs and create a unique rule for each possible set of inputs. Then, when
  765. actually processing, it will compare the inputs against all the possible
  766. masks, choosing as the answer the closest match. This is referred to as fuzzy
  767. logic and is a powerful tool. The expert can even guess - pretty accurately!
  768.  
  769. Out expert system, at this stage of evolution, will be menu driven. It
  770. firsts prompts for the total number of variable and results. The variables
  771. are the questions is will ask, the results are the answers it will give.
  772. To use the expert, think of some group of things or some problem that
  773. you will want the expert to differentiate between. This can by anything
  774. from why your car wont start, to medical diagnosis to psychology. Keep
  775. this in mind when entering the variables. Phrase each variable in the form
  776. of an attribute - something the expected result would posses, which describes
  777. it. For example, if one of your results is to be a CAR then a variable
  778. might be TIRES or WINDSHIELD. Keep all you dialog similar.
  779.  
  780. After you select option 3, run expert, the expert system will prompt
  781. for each variable whether it is true or false. You must have a desired
  782. answer in mind when answering these prompts. This is because at this
  783. early stage the expert knows nothing! You need to train it first. We
  784. do this by you thinking of one of the results, then answering the
  785. variable prompts for that response. Initially the expert will be wrong.
  786. But as you give it examples of each response, the expert begins to learn.
  787. Finally, when fully trained, it will have learned to tell each result
  788. from the answers to the questions it asks. After each run, select option
  789. 1, display rules. What you will see is a matrix of the results to variables.
  790. As you use the expert, and it learns, this matrix will change. You will
  791. see positive numbers for true conditions and negative numbers for false
  792. conditions.
  793.  
  794. While the expert in this stage of development is not directly usable in
  795. another program, think about what it can do. Given some set of inputs and
  796. outputs, and experience, the expert can accurately make deductions. And
  797. if is wrong it can modify itself to induce corrective actions. Now think
  798. about using such an expert in conjunction with a database or a spreadsheet.
  799. You begin to understand the power. For example you could build a credit
  800. risk assessment expert linked to a database, or a diagnostic system to
  801. recommend repair activities. The sky is the limit.
  802.  
  803. Next month we are going to add a new feature known as NODES. This will
  804. allow our expert to use it's deductions as inputs to another deduction.
  805. This addition will allow our expert to analyze a situation, determine a
  806. course of action and then recommend another course of action based upon
  807. it's first deduction.
  808.  
  809. The code below is for a fully functional, working expert system. Take a
  810. look at it. Each month we will add new parts, including a save to disk
  811. option, and more. For now though it is fully functional and is able to learn
  812. from its experience.
  813. Use the Cut segment command from the main utilities menu to save this
  814. file to disk. Give it a name like EXPERT1.BAS so you can keep them
  815. straight each month. When you load this into BASIC, delete all of the
  816. text lines above. The program starts immediately below.
  817.  
  818.  ' ------------------------------------------------------------------
  819.  '
  820.  '(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
  821.  'You may use this program for anything or any purpose including inclusion
  822.  'into programs you write BUT you cannot sell this source code. Written by
  823.  'Hank Marquis.
  824.  
  825.  DEFINT A-Z
  826.  CLS
  827.  
  828.  ' To make this a little easier for you, below I have stated the name &
  829.  ' function of each array and variable .
  830.  '
  831.  '  VARIABLE                  NAME                        FUNCTION
  832.  '  --------                  ----                        --------
  833.  '  MV              NUMBER OF VARIABLES      THE MAX NUMBER OF VARIABLES
  834.  '  MR              NUMBER OF RESULTS        THE MAX NUMBER OF RESULTS
  835.  '  HI              HIGHEST RESULT           POINTS TO THE MOST LIKELY NR()
  836.  '  D               HIGHEST RESULT           HOLDS LAST BEST GUESS
  837.  '
  838.  '  NR$(X)          NUMBER OF RESULTS        HOLDS THE CURRENT RESULT FOR
  839.  '  R(X,Y)          RULE ARRAY               HOLDS THE RULE FOR EACH (X,Y)
  840.  '  D(D)            DECISION ARRAY           HOLDS THE CURRENT BEST GUESS
  841.  '  NV(X)           NUMBER OF VARIABLES      POINTS TO THE VARIABLE FOR CASE
  842.  '  NV$(X)          RESULT REGISTER          FOR (X,Y) HOLDS THE ACTUAL
  843.  '                                           RESULT.
  844.  
  845.  
  846.  'Setup expert definition-----
  847.  '
  848.  'To start use 4 variables
  849.  INPUT "How many variables "; MV
  850.  
  851.  'To start use 3 results
  852.  INPUT "How many results "; MR
  853.  PRINT
  854.  
  855.  REDIM NR$(MR), R(MV, MR), D(MR), NV(MV), NV$(MV)
  856.  
  857.  'lets get the expert to learn to tell the difference between a plane
  858.  ' a boat and a car. So for the 4 variables give the expert the following
  859.  ' responses :
  860.  '   Variable 1 name : wings
  861.  '   Variable 2 name : sails
  862.  '   Variable 3 name : tires
  863.  '   Variable 4 name : motor
  864.  FOR I = 1 TO MV
  865.    PRINT "Variable"; I; "name : ";
  866.    LINE INPUT ; NV$(I)
  867.    PRINT
  868.  NEXT 'I
  869.  PRINT
  870.  'For the three responses give it
  871.  '  Result 1 name : car
  872.  '  Result 2 name : boat
  873.  '  Result 3 name : plane
  874.  
  875.  FOR I = 1 TO MR
  876.    PRINT "Result"; I; "name : ";
  877.    LINE INPUT ; NR$(I)
  878.    PRINT
  879.  NEXT 'I
  880.  
  881. 'MENU SYSTEM -----
  882.  
  883.  DO
  884.  
  885.  'Select option 1 to display the rules the expert develops
  886.  ' during its use
  887.  'Option 2 quits
  888.  'Option 3 starts the expert and asks you for the input regarding the
  889.  ' four variables, then it makes its guess.
  890.  
  891.  PRINT
  892.  PRINT " 1 - Display rules"
  893.  PRINT " 2 - Exit"
  894.  PRINT " 3 - Run expert"
  895.  X = VAL(UCASE$(INPUT$(1)))  'this is a quick way of getting one key
  896.                              '  stroke from BASIC
  897.  
  898.  SELECT CASE X
  899.   CASE 1
  900.     'Prints a matrix of the relationship between inputs and outputs.
  901.     PRINT
  902.     FOR I = 1 TO MV
  903.      FOR J = 1 TO MR
  904.       PRINT R(I, J); " ";
  905.      NEXT 'J
  906.      PRINT
  907.     NEXT 'I
  908.  
  909.   CASE 2
  910.     'as implies, ends program!
  911.     END
  912.   CASE 3
  913.     CLS
  914.     D = 0  'set last best guess to 0
  915.     'this FOR...NEXT loop resets the expert for each question
  916.     FOR I = 1 TO MR
  917.       D(I) = 0
  918.     NEXT 'I
  919.     GOSUB Engine  'run the expert system engine
  920.  END SELECT
  921.  LOOP
  922.  
  923.  END
  924.  
  925.  
  926. Engine: '------------------------------------------------
  927.  '
  928.  'This is the main expert system code - called the inference engine
  929.  ' 1) we get the user input
  930.  ' 2) build a mask or rule
  931.  ' 3) make a guess
  932.  ' 4) and if wrong learn
  933.  
  934. 'Get User Input------------------------------
  935.  
  936. FOR I = 1 TO MV           'REPEAT SUB FOR "MV" MAXIMUM NUMBER OF VARIABLES
  937.  
  938.  'ASK ABOUT THE CURRENT VARIABLE
  939.  
  940.  DO
  941.    PRINT "Is variable"; I; "("; NV$(I); ") [T]rue or [F]alse?"
  942.    YN$ = UCASE$(INPUT$(1))
  943.  
  944.    SELECT CASE YN$
  945.     CASE "T"
  946.       'If the variable is true then set the array NV(I) to a 1 indicating
  947.       ' that the result possess this attribute of NV(I)
  948.       NV(I) = 1
  949.       EXIT DO
  950.     CASE "F"
  951.       'If the variable is false then set the rules array to a 0 indicating
  952.       ' that the result does NOT possess this attribute of NV(I)
  953.       NV(I) = 0
  954.       EXIT DO
  955.    END SELECT
  956.  
  957.  LOOP
  958.  
  959. NEXT 'I
  960.  
  961.  
  962.  'Now NV() holds a mask which represents the characteristics of the
  963.  ' given item, boat, plane or car. For each attribute which is true
  964.  ' NV(x) = 1 and for each attribute which is false, NV(x) = 0.
  965.  '
  966.  'for example if you had answered the prompts thinking of a car then NV()
  967.  ' looks like this:
  968.  ' NV(1) = 0   NO - wings
  969.  ' NV(2) = 0   NO - sails
  970.  ' NV(3) = 1   YES - tires
  971.  ' NV(4) = 1   YES - motor
  972.  '
  973.  'We know have an attribute mask of '0011' for car. Now lets see if the
  974.  ' expert can learn to tell them apart.
  975.  
  976. 'BUILD A RULE-------------------------------
  977.  
  978. FOR I = 1 TO MV                 'REPEAT FOR MAXIMUM NUMBER OF VARIABLES
  979.  FOR J = 1 TO MR                'REPEAT FOR MAXIMUM NUMBER OF RESULTS
  980.  
  981.    'Here is where we build a rule for this attempt. We multiply the
  982.    ' experts memory which is array R() times the mask held in NV().
  983.    ' If the mask attribute is 0 then the value held in R() is not
  984.    ' added to the decision array D() for this response. If NV() holds a 1
  985.    ' then D() has the value of R() added to it. In this way we are
  986.    ' actually testing each possible result against the mask array NV()
  987.    '
  988.  
  989.    D(J) = D(J) + NV(I) * R(I, J) 'PERFORM RULE
  990.  
  991.  NEXT 'J
  992. NEXT  'I
  993.  
  994.   ' Now D() holds a value for each result. The value is greater for each
  995.   ' attribute of the item for which NV() was a 1. The highest value of D()
  996.   ' is the best, most likely answer for the given responses held in the
  997.   ' mask array NV()
  998.  
  999. 'MAKE AN EDUCATED GUESS! ----------------------------------------------
  1000.  
  1001. FOR I = 1 TO MR                 'REPEAT FOR MAXIMUM NUMBER OF RESULTS
  1002.  
  1003.  IF D(I) > D OR D(I) = D THEN   'IS D(X) = TO 1 OR -1 ?
  1004.    D = D(I)                     'IF 1 OR -1 THEN ASSIGN IT AS BEST GUESS
  1005.    HI = I
  1006.  END IF
  1007.  
  1008. NEXT 'I
  1009.  
  1010.  'Now HI and D hold the number of the result which scored highest against
  1011.  ' the mask array NV(). We now have the experts guess as to which result
  1012.  ' is the answer - it is result NR$(HI)
  1013.  
  1014. 'ASK IF IT'S A CORRECT ASSUMPTION -------------------------------------
  1015.  
  1016. PRINT
  1017. PRINT "Is the answer "; NR$(HI); "? [Y/N]"   'lets see if we are right
  1018. a$ = UCASE$(INPUT$(1))                       'get a Y/N from user
  1019. IF a$ = "Y" THEN RETURN 'bug out - we are right!
  1020. 'IF IT IS NOT CORRECT ADJUST THE RULES (LEARN) ------------------------
  1021.  'The user did not indicate that this was the right answer, so it
  1022.  ' must be wrong. Get the right answer from the user.
  1023. PRINT
  1024. PRINT "Which result was it? [select from 1 to"; STR$(MR); "]"
  1025. FOR I = 1 TO MR                 'DISPLAY ALL THE POSSIBLE RESULTS
  1026.  PRINT I; "- "; NR$(I)
  1027. NEXT 'I
  1028.  
  1029. B = VAL(INPUT$(1))              'GET THE USERS RESPONSE
  1030.  
  1031.  'B now holds the CORRECT result number, which we just got from the
  1032.  ' user.
  1033.  'For all the possible results, if the value of D() calculated above
  1034.  ' is greater or equal to the best guess D, then we need to reduce the
  1035.  ' value of this rule, as it was wrong.
  1036.  
  1037. FOR I = 1 TO MR
  1038.  IF D(I) > D OR D(I) = D AND I <> B THEN
  1039.    FOR J = 1 TO MV
  1040.  
  1041.      'Remove the value of NV() from the rule array R(I,J) for this
  1042.      ' result. This has the effect of further separating the
  1043.      ' masks - making the expert more accurate. This is the actual
  1044.      ' learning feedback process.
  1045.  
  1046.      R(J, I) = R(J, I) - NV(J)
  1047.  
  1048.    NEXT 'J
  1049.  END IF
  1050.  
  1051. NEXT 'I
  1052.  
  1053. FOR J = 1 TO MV
  1054.   'As an extra measure, lets add the value of NV() to the correct
  1055.   ' result, giving us now a clear separation between the right
  1056.   ' result and the wrongly guessed result. The expert has now learned.
  1057.  
  1058.   R(J, B) = R(J, B) + NV(J)
  1059.  
  1060. NEXT 'J
  1061.  
  1062. 'FINISH----------------------
  1063.  
  1064.  RETURN
  1065.  
  1066. 'END OF EXPERT SYSTEM CODE-----------------------
  1067. END LONG TERM LONG TERM
  1068. THE BASICS THE BASICS
  1069. THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS
  1070.  
  1071. In this section we will cover topics of interest to new or intermediate
  1072. users of BASIC. This month, the topic is DOS Disk Files, an introduction
  1073. to how DOS manages files.
  1074.  
  1075. Sectors & Clusters
  1076. When a file is written to disk in DOS, it is divided into pieces,
  1077. called sectors. Each sector is typically 512 bytes long. A byte
  1078. refers to the grouping of eight bits - 10001101 is a byte.
  1079. Several sectors are then combined into a unit called a cluster.
  1080. The number of sectors per cluster depend upon the size of the
  1081. disk. When a disk is formatted, this information is stamped on
  1082. the disk in a special location, called the boot sector or BIOS
  1083. Parameter Base. Each cluster represents a given amount of disk
  1084. space. Depending on disk type (floppy or hard) and disk size
  1085. the number of sectors per cluster in different.
  1086.  
  1087.        cluster
  1088.        +---------------------------------------+
  1089.        |         |         |         |         |
  1090.        |         |         |         |         |
  1091.        |         |         |         |         |
  1092.        |         |         |         |         |
  1093.        |         |         |         |         |
  1094.        +---------------------------------------+
  1095.            +-------------------------------------> SECTORS
  1096.  
  1097.  
  1098. file storage
  1099. When a file is written to a disk, it is done so in blocks of
  1100. bytes. In fact is written a sector at a time. Even if the file
  1101. doesn't fill up a sector, it still uses an entire sectors worth of
  1102. storage space. And, in turn, using one sector out of a cluster
  1103. uses that entire cluster. For example, if a disk used 512 byte
  1104. sectors (which is what most IBM disks use) and we have 4 sectors
  1105. per cluster (again, common on most hard drives) then the minimum
  1106. disk space needed for any file is 512 bytes X 4 sectors/cluster
  1107. or 2048 bytes! If the file was a very small batch file of only 30
  1108. bytes, it would really use 2048 bytes of disk space.
  1109.  
  1110.        cluster
  1111.        +---------------------------------------+
  1112.        |This is  |systems. |part of  |.........|
  1113.        |how data |         |the      |.........|
  1114.        |is stored|If the   |cluster  |.........|<-- wasted or slack
  1115.        |on most  |file only|the rest |.........|    area
  1116.        |disk     |uses a   |is waste |.........|
  1117.        +---------------------------------------+
  1118.            +-------------------------------------> SECTORS
  1119.  
  1120.  
  1121. Now it isn't really using all that space, it's just allotted that much space
  1122. due to the way that DOS organizes disks. When you use the DOS DIR
  1123. command to display a file, you see the actual size of the file -
  1124. not how much space it is really using. The method which DOS uses
  1125. to add to a file on a hard of floppy disk is determined by the
  1126. version of DOS which is being used. Early versions of DOS (before
  1127. DOS version 3.0) used any next available cluster when a file was
  1128. appended or added to. Later versions of DOS try to allocate new
  1129. clusters which are contiguous - or together in a row. It is
  1130. important to understand that as a file grows, the clusters which
  1131. hold its data are often not one after the other. As shown above,
  1132. as a file grows new clusters are added. If there are no more available
  1133. clusters which are contiguous, DOS jumps to the next open cluster.
  1134. This process is referred to a fragmentation.
  1135.  
  1136. Keeping track of file allocations
  1137. DOS employs a simple system of tracking the clusters in use by a
  1138. file. This system is comprised of two parts - the File
  1139. Allocation Table (FAT) and the directory entry. First, the
  1140. directory entry. You see part of a DOS directory entry when you
  1141. use the DOS DIR command to display files. The entry contains the
  1142. file name, its size, its attribute and its starting cluster
  1143. number. The directory entry containing the file name also holds
  1144. the status of the file. If the file is erased, then the first
  1145. character in the file name is the Greek letter omega. If the first
  1146. character in a file name is omega then DOS doesn't display its data.
  1147. DOS uses the starting cluster number of a files directory entry to
  1148. identify the first cluster which holds this files data. The
  1149. starting cluster number is converted into an offset which points
  1150. to another special region of the disk, referred to as the File
  1151. Allocation Table or FAT. The FAT comes right after the BIOS
  1152. Parameter Base of boot sector of a disk. It is a matrix or table
  1153. of numbers, called Cells. Each number points to a cluster on the
  1154. disk. There may be more than one FAT and often there are two.
  1155.  
  1156.  
  1157.    cluster 0
  1158.                 +---------------------+
  1159.                 | BIOS Parameter Base |
  1160.                 | or the BOOT sector  |      A DOS disk is laid out
  1161.                 |                     |      like this.
  1162.                 |---------------------|
  1163.                 | File Allocation     |
  1164.                 | Tables (FATS)       |
  1165.                 |                     |
  1166.                 |---------------------|
  1167.                 | ROOT Directory      |
  1168.                 |                     |
  1169.                 |---------------------|
  1170.                 | File space          |
  1171.                 |                     |
  1172.  
  1173.    cluster n
  1174.  
  1175. Right after the FAT table(s) there is the root directory space. It is a fixed
  1176. amount of disk space given the task of storing files and sub-directory
  1177. entries. You can only have a limited amount of files and sub directories
  1178. in the root because the root directory size if fixed. Other directories and
  1179. their entries are stored as files in other clusters, randomly around the disk.
  1180. Each FAT cell has a number, known by its position in the
  1181. table, which represents its cluster. FAT cell 1 is for cluster number 1, FAT
  1182. cell 2 for cluster 2 etc.. Every cluster has an entry in the FAT, and each
  1183. FAT cell points to a cluster. In addition, each FAT cell can hold a value.
  1184. These values are 0, the number of another FAT cell or the hexidecimal number
  1185. FFF for a floppy or FFFF for a hard disk. If the FAT cell for a cluster
  1186. is set to 0, then this FAT cells cluster is not in use. If the FAT cell
  1187. contains a number of a legal cluster number, then the number is the next
  1188. cluster containing data for this file. If the number is hex FFF or FFFF
  1189. then this FAT cell cluster is the last cluster in the file.
  1190.  
  1191.    Example File Allocation chain for file SAMPLE.TXT
  1192.  
  1193.         FAT CELL1 FAT CELL2 FAT CELL3 FAT CELL4
  1194.        +---------------------------------------+
  1195.        |         |         |         |         |
  1196.        |   2     |    4    |   FFFF  |   FFFF  |
  1197.        |         |         |         |         |
  1198.        +---------------------------------------+
  1199.            |         |          |        |
  1200.            |         |          |        |
  1201.            |         |          |        |
  1202.            |         |          |        +>FAT cell 4 holds the value 65535
  1203.            |         |          |          or HEX FFFF. For hard disks this
  1204.            |         |          |          means the end of the file.
  1205.            |         |          |
  1206.            |         |          +> FAT cell 3 is used by another file
  1207.            |         |
  1208.            |         +>FAT cell 2 points to FAT cell 4
  1209.            |
  1210.            +>FAT cell 1 points to FAT cell 2 as the next cell in the chain
  1211.              for our example file.
  1212.  
  1213.  
  1214.        In the above diagram, the FAT chain for our file is 1 - 2 - 4.
  1215.        Each FAT cell points to a cluster, depending on an algorithm to
  1216.        convert FAT cell addresses to clusters. To read the file DOS goes
  1217.        along the FAT chain, reading each FAT cell, converting the FAT cell
  1218.        address into a cluster, and then reading that cluster.
  1219.  
  1220.  
  1221. Erased files
  1222. When DOS erases a file, it simply deletes the FAT chain by writing 0
  1223. to all the cells of the file. Then DOS adds the character omega as the
  1224. first character of the file name.
  1225.  
  1226.     Erased file SAMPLE.TXT old FAT chain
  1227.  
  1228.         FAT CELL1 FAT CELL2 FAT CELL3 FAT CELL4
  1229.        +---------------------------------------+
  1230.        |         |         |         |         |
  1231.        |   0     |    0    |    0    |    0    |
  1232.        |         |         |         |         |
  1233.        +---------------------------------------+
  1234.     Erased files directory entry
  1235.  
  1236.      file name  attributes  create date  create time  size  starting cluster
  1237.     σAMPLE.TXT    HIDDEN     01-01-1990    12:00:00   8723        1212
  1238.     |
  1239.     +> this is the omega character - when in the first character
  1240.        position of a files name, it means the file is deleted
  1241.  
  1242. You can't see it or modify once this happens. A very important fact - the
  1243. data in the disk sectors that were this file it still there. DOS does NOT
  1244. remove the data when a file is deleted. It just erases it's FAT chain and
  1245. changes the directory entry as shown above. It is then possible to UNERASE
  1246. a deleted file! To unerase a file, you need to change the files directory
  1247. entry, and rebuild the files FAT cell chain. Unfortunately, this isn't
  1248. always so easy. Erased files may be recovered - we will build the routines
  1249. and a file unerase program to do this in another issue. But think of this
  1250. - how do we know which FAT cell is for which file if each erased files FAT
  1251. cell is a zeroed out and not pointing to another cell!? Well, that's a
  1252. story for another day...Hank Marquis
  1253. END THE BASICS THE BASICS
  1254. ADVANCED BASIC ADVANCED BASIC
  1255. ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADV
  1256.  
  1257. This segment is dedicated to an in-depth study and application of an advanced
  1258. programming topic. This month we are going to take a look at using interrupts
  1259. in QuickBASIC to call low level DOS functions directly.
  1260.  
  1261. Using interrupts offers all the power of DOS to your programs. With such
  1262. power comes a responsibility - using the wrong interrupt or specifying
  1263. invalid or wrong pointers can lose data or damage your hard disk! I always
  1264. fully test ANY operation which writes to disk on a floppy BEFORE I try it
  1265. on a hard disk and even then I always back up by hard disks first.
  1266.  
  1267. What is a BASIC interrupt? Well, this function of BASIC lets us directly
  1268. communicate with the CPU and all of its registers and flags. We are able
  1269. to load registers with values, tell the CPU to execute to some DOS function
  1270. and then read the results. This is a very powerful operation. You have
  1271. access to every DOS system call. There is almost nothing that cant be done
  1272. using these DOS system calls in part or whole.
  1273.  
  1274. Read the segment in this months BASICS. It discusses the File Allocation
  1275. Table and how DOS manages disks. Well, now lets use the BASIC CALL Interrupt
  1276. function to actually read the BPB - BIOS Parameter Base - of a disk. As we
  1277. are only going to read from the disk, this is a safe use of interrupts.
  1278. For those souls seeking a higher level than I can provide here, I urge you
  1279. to get a copy of this months book review - ADVANCED MSDOS: The Microsoft
  1280. guide for Assembly language and C (there they go again...) programmers.
  1281. Advanced MS-DOS covers all the DOS functions and how they are called. It
  1282. also has passable explanations of the BPB and other interesting sections.
  1283. First, just what is the BPB? The BPB resides just after the OEM name and
  1284. version bytes, which in turn is just after the JUMP byte. The JUMP byte
  1285. holds a command which tells the computer that the disk is formatted and
  1286. points to a region of disk space located after the BPB. The BOOT Sector
  1287. contains all the above.
  1288.  
  1289.                       Item              byte offset     length
  1290.                                         into sector 0   in bytes
  1291.                 +---------------------+
  1292.                 | JUMP INSTRUCTION    |  0               1
  1293.                 |---------------------|
  1294.                 | OEM NAME & VERSION  |  4               8
  1295.                 |                     |
  1296.  
  1297.  Start of BPB   +---------------------+
  1298.                 | bytes/sector        |  12              2
  1299.                 |---------------------|
  1300.                 | sectors/cluster     |  14              1
  1301.                 |---------------------|
  1302.                 | reserved sectors    |  15              2
  1303.                 |---------------------|
  1304.                 | number of FATS      |  17              1
  1305.                 |---------------------|
  1306.                 | max. root entries   |  18              2
  1307.                 |---------------------|
  1308.                 | total sectors       |  20              2
  1309.                 |---------------------|
  1310.                 | media descriptor    |  22              1
  1311.                 |---------------------|
  1312.                 | sectors/FAT         |  23              2
  1313.                 |---------------------|
  1314.                 | sectors/disk track  |  25              2
  1315.                 |---------------------|
  1316.                 | number disk heads   |  27              2
  1317.                 |---------------------|
  1318.  End of BPB     | hidden sectors      |  29              2
  1319.                 |                     |
  1320.  
  1321.                 +---------------------+
  1322.                 | bootstrap code      |
  1323.  
  1324.  
  1325. The boot Sector is logical sector 0. The boot sector holds much information
  1326. but here we are only looking at the BPB. The BPB contains the information
  1327. as the physical structure of the disk under use. It holds information
  1328. to allow FAT calculations and hence read or write any file to the disk.
  1329. We are now going to write a routine which will get the BPB from DOS, using
  1330. the BASIC Call Interrupt routine and take a look into our own BPB.
  1331. Following is a sub routine which reads the BPB, returning all the
  1332. information it contains. By itself it isn't to much use - more interesting
  1333. than useful. But as we go ahead in other issues, this sub routine becomes
  1334. critical in building our unerase program!
  1335.  
  1336. The call is :
  1337.  
  1338.  drive$ = "C"
  1339.  GetDOSBoot drive$, BPB, Regs, Status
  1340.  drive$ = the drive letter (A,B,C,D etc) we want to read
  1341.  BPB    = the DOS Boot type array (DBType)
  1342.  Regs   = the CPU register type array
  1343.  Status = a flag which, if not zero, indicates some sort of error
  1344.           occurred during the call.
  1345.  
  1346. The code below will ONLY work under QuickBASIC. The PDS uses a different
  1347. string memory management technique referred to as 'far strings'. Under a
  1348. system using far strings all pointer contain to parts - a SEGMENT and an
  1349. OFFSET. The segment points to a 64KB block of memory, the offset points to
  1350. a distinct area within that segment. In the code below, the SADD function
  1351. of QuickBASIC version 4.5 is used. It points to the offset only - QB4.5 does
  1352. not use far strings - all strings are in one, known segment. To modify
  1353. this program for use with the PDS you will need to also use the InteruptX
  1354. routine, not Interrupt. Under InteruptX you will need to use the DS register
  1355. as follows :
  1356.  
  1357.   DS = segment of string
  1358.   BX = offset of string
  1359.  
  1360. After the program, we can display the results. A complete program is below.
  1361. Here is the complete working program. Cut this out and load it into
  1362. BASIC, then run it. You will need to load the quick library QB.QLB that
  1363. came with QB. To load a quick library, start QuickBASIC as follows
  1364.  
  1365.  QB /L QB
  1366.  
  1367. Use the Cut segment command from the main utilities menu to save this
  1368. file to disk. Give it a name like DOSBOOT1.BAS so you can keep them
  1369. straight each month. When you load this into BASIC, delete all of the
  1370. text lines above. The program starts immediately below.
  1371.  
  1372.  'start of program---------------------------------------------------------
  1373.  '
  1374.  '(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
  1375.  'You may use this program for anything or any purpose including inclusion
  1376.  'into programs you write BUT you cannot sell this source code. Written by
  1377.  'Hank Marquis. revised 9/8/90.
  1378.  
  1379.   DEFINT A-Z
  1380.  
  1381.   DECLARE SUB Interrupt (IntToCall, Regs AS ANY, Regs AS ANY)
  1382.   DECLARE SUB GetDOSBoot (drive$, BPB AS ANY, Regs AS ANY, Status%)
  1383.  
  1384.   'To use the CALL Interrupt routine we first must build some TYPE arrays.
  1385.   'QuickBASIC supports a Interrupt and InteruptX call, I have used the
  1386.   'Interrupt here. The difference is that InteruptX gives access to all the
  1387.   'registers and flags. We don't need them all, but we can build a type
  1388.   'array with them anyway - always better to think of the future.
  1389.  
  1390.   'Sometimes you will see reference to AX or AH or AL. These are the
  1391.   ' same register! What they mean are which byte of the 16 bit register
  1392.   ' we are talking about - AX is all sixteen bits, AH is the high order
  1393.   ' 8 bits and AL is the low order 8 bits. The same is true for BX, BH
  1394.   ' BL and the others.
  1395. TYPE RegType
  1396.     'This type defines all 80XXX type registers, pointers and
  1397.     ' flags. We are not going to use all of them in this
  1398.     ' program, but it doesn't hurt to be complete.
  1399.     AX AS INTEGER
  1400.     BX AS INTEGER
  1401.     CX AS INTEGER
  1402.     DX AS INTEGER
  1403.     BP AS INTEGER
  1404.     SI AS INTEGER
  1405.     DI AS INTEGER
  1406.     FLAGS AS INTEGER
  1407.     ES AS INTEGER
  1408.     DS AS INTEGER
  1409.   END TYPE
  1410.  
  1411.   'Next we need to build a file info type array to hold all the DOS boot
  1412.   ' sector or BPB information that our sub routine will return.
  1413.  
  1414.   TYPE DBType
  1415.     'Each item in the BPB will be returned using this type. The names of
  1416.     ' the type elements are accurate descriptions of the BPB data they
  1417.     ' return.
  1418.     OEM AS STRING * 8
  1419.     BytesPerSector AS INTEGER
  1420.     SectorsPerCluster AS INTEGER
  1421.     ReservedSectors AS INTEGER
  1422.     NumberOfFats AS INTEGER
  1423.     RootEntries AS INTEGER
  1424.     Sectors AS LONG
  1425.     Clusters AS LONG
  1426.     MediaType AS STRING * 45
  1427.     SectorsPerFAT AS INTEGER
  1428.     SectorsPerTrack AS INTEGER
  1429.     NumberOfHeads AS INTEGER
  1430.     NumberHiddenSectors AS INTEGER
  1431.     StartOFRoot AS INTEGER
  1432.     StartOFFAT AS LONG
  1433.     StartOfData AS INTEGER
  1434.     SizeOfDir AS INTEGER
  1435.     SizeOfFile AS DOUBLE
  1436.   END TYPE
  1437.  
  1438.   'Now lets DIM the type arrays so that we can use them.
  1439.   DIM BPB AS DBType
  1440.   DIM Regs AS RegType
  1441.  
  1442.   'this program reads the DOS boot sector or more accurately sector 0 of a
  1443.   'drive and determines the disk file allocation system & capacities - this
  1444.   'part of the drive is called the BIOS Parameter Block or BPB
  1445.  
  1446.   COLOR 7, 0
  1447.   CLS
  1448.   PRINT "Read & display the BIOS Parameter Base (boot sector)"
  1449.   PRINT "Select letter of drive to display [ABCDE etc]"
  1450.  
  1451.   drive$ = UCASE$(INPUT$(1))                    'pick a drive, any drive...
  1452.   GetDOSBoot drive$, BPB, Regs, Status          'Get the Boot Sector & parse
  1453.                                                 ' it up
  1454.  
  1455.   IF Status THEN                                'something bad happened
  1456.     END                                         'end program
  1457.   END IF
  1458.  
  1459.   'This block of print commands is optional. It is put here to let you
  1460.   ' see the information returned by the GetDOSBoot sub routine.
  1461.  
  1462.   PRINT
  1463.   PRINT "Boot sector information for drive "; drive$; ":\"
  1464.   PRINT STRING$(60, "-")
  1465.   PRINT "DOS OEM label      "; BPB.OEM
  1466.   PRINT "Bytes Per Sector  "; BPB.BytesPerSector
  1467.   PRINT "Sectors/Cluster   "; BPB.SectorsPerCluster
  1468.   PRINT "Clusters           ";
  1469.   PRINT USING "##,###"; BPB.Clusters
  1470.   PRINT "Reserved Sectors  "; BPB.ReservedSectors
  1471.   PRINT "Number FATS       "; BPB.NumberOfFats
  1472.   PRINT "Root Dir Entries  "; BPB.RootEntries; "(maximum)"
  1473.   PRINT "Sectors            ";
  1474.   PRINT USING "##,###"; BPB.Sectors&
  1475.   PRINT "Media Descriptor   "; BPB.MediaType
  1476.   PRINT "Sectors Per FAT   "; BPB.SectorsPerFAT
  1477.   PRINT "Hidden Sectors    "; BPB.NumberHiddenSectors
  1478.   PRINT "Start of FAT       "; "sector #"; BPB.StartOFFAT
  1479.   PRINT "Start of data      "; "sector #"; BPB.StartOfData
  1480.   PRINT "Drive capacity    ";
  1481.   PRINT USING "###,###,###"; BPB.BytesPerSector * BPB.Sectors&;
  1482.   PRINT " (bytes)"
  1483.   PRINT "Sectors/track     "; BPB.SectorsPerTrack
  1484.   PRINT "Drive heads       "; BPB.NumberOfHeads
  1485.  
  1486.   END
  1487.  
  1488. DEFINT A-Z
  1489. SUB GetDOSBoot (drive$, BPB AS DBType, Regs AS RegType, Status)
  1490.  
  1491.  'This sub reads the DOS BIOS Parameter Base (BPB) and determines
  1492.  ' the basic disk configuration of sectors, clusters, FATs, disk
  1493.  ' type, root entries, disk size etc..,
  1494.  
  1495.  '-Setup error trap-------------------------------------------------------
  1496.    Status = 1    'set error flag for error - guilty until proven innocent
  1497.  
  1498.  'Convert drive into a number for DOS-------------------------------------
  1499.    drive$ = LEFT$(drive$, 1)            'drive$ should be A,B,C etc.,
  1500.    drive = ASC(UCASE$(drive$)) - 65     'change drive to a number where
  1501.                                         ' A = 1, B = 2 etc.,
  1502.  
  1503.  'Set disk sector size----------------------------------------------------
  1504.    'An IBM disk sector size is - 512 bytes. But what if we didn't know
  1505.    ' that? There is a DOS function call which can return the number of
  1506.    ' bytes per sector. It is Int &21H function &H36.
  1507.    '
  1508.    'You use it as follows:
  1509.    '
  1510.    '  Regs.AX = &H3600              'function to call
  1511.    '  Regs.DX = Drive               'drive where 0=A, 1=B etc.,
  1512.    '  Interrupt &H21, Regs, Regs    'use DOS general interrupt &H21
  1513.    '
  1514.    'This call returns the following information:
  1515.    '
  1516.    '  Regs.AX = sectors per cluster
  1517.    '  Regs.BX = number of available clusters
  1518.    '  Regs.CX = bytes per sector
  1519.    '  Regs.DX = cluster per drive
  1520.    '
  1521.    'Now this information is also in it's raw form in the BPB, so we are
  1522.    '  going to use what we find in the BPB, even though this call could
  1523.    '  find out some of the same information. We are just using this call
  1524.    '  to get the number of bytes per sector.
  1525.    '-------------------------------------------------------------------
  1526.  
  1527.    Regs.AX = &H3600                'function &H36 - get free disk space
  1528.    Regs.DX = Drive + 1             'use drive entered + 1 because
  1529.                                    ' this function uses a slightly
  1530.                                    ' different syntax than INT &H25. &H36
  1531.                                    ' uses 0 for default, 1 for A etc.,
  1532.                                    ' &H25 uses 0 for A, 1 for B etc.,
  1533.  
  1534.    Interrupt &H21, Regs, Regs      'call &H21 - general DOS functions
  1535.  
  1536.    BytesInSector = Regs.CX         'Regs.CX now holds bytes per sector
  1537.  
  1538.    IF Regs.CX = 0 THEN EXIT SUB    'something bad happened so boogy...
  1539.  
  1540.    'Now BytesInSector is the size of a sector. We will use this to make
  1541.    '  a string exactly one sector long to receive the BPB from the next
  1542.    '  DOS call we make.
  1543.  
  1544.  'Read DOS Base Pointer (BPB)---------------------------------------------
  1545.  
  1546.    'Most interrupts added in DOS 3.0 and up, though not all, use a null
  1547.    ' terminated string for these calls. We do that in BASIC by appending
  1548.    ' CHR$(0) to the end of the string, as shown below. This is referred to
  1549.    ' to as ASCIIZ
  1550.    '
  1551.    'DOS interrupt &H25 is the DOS absolute read function. It will read
  1552.    ' as many sectors as you ask it for. We are going to read the whole
  1553.    ' boot sector - which is BytesInSector long - from above. We need to
  1554.    ' pre-fill a string to recieve the sector data first. That is why
  1555.    ' we needed to determine BytesInSector BEFORE calling this routine.
  1556.    '
  1557.    'We call it with the following parameters under QB 4.5
  1558.    ' AX = drive to read (0 = A etc.,)
  1559.    ' BX = pointer to pre-filled ASCIIZ string to receive disk information
  1560.    ' CX = quantity of sectors to read
  1561.    ' DX = absolute disk sector number to start reading from to read
  1562.    '
  1563.    'To use this routine under QBX you will need to make some changes. This
  1564.    ' is due to QBX use of far strings. You need to pass the segment as
  1565.    ' well the offset. You will need to use the SSEG() function.
  1566.    '
  1567.    ' DS = segment of string or Regs.DS = SSEG(string$)
  1568.    '
  1569.    '
  1570.    '----------------------------------------------------------------------
  1571.  
  1572.    DOSBOOT$ = SPACE$(BytesInSector) + CHR$(0)   'ASCIIZ to receive BPB data
  1573.                                                 ' prefilled to correct
  1574.                                                 ' length with spaces.
  1575.  
  1576.    Regs.AX = drive                              'Set drive to get from
  1577.  
  1578.    Regs.BX = SADD(DOSBOOT$)                     'set BX to point to location
  1579.                                                 ' in memory of DOSBOOT$
  1580.  
  1581.    'using SADD() above points to the memory location of the string. Here we
  1582.    ' are not actually working on the string itself - we are working on it
  1583.    ' indirectly using what is referred to as a "pointer"
  1584.  
  1585.    Regs.CX = 1                                  'read '1' sector
  1586.    Regs.DX = 0                                  'start reading at sector
  1587.                                                 '  '0' boot sector
  1588.    Interrupt &H25, Regs, Regs                   'call DOS-absolute read
  1589.  
  1590.    IF DOSBOOT$ = SPACE$(BytesInSector) + CHR$(0) THEN EXIT SUB
  1591.      'the above line is true if there was an error - so if there was
  1592.      ' an error lets boogy... Note that Status was preset to indicate
  1593.      ' an error - so the calling program knows that this call was
  1594.      ' unsuccessful by default.
  1595.  
  1596.    'If we got here then DOSBOOT$ now holds the DOS boot record!
  1597.  
  1598. 'Parse BPB info------------------------------------------------------------
  1599.  
  1600.    'If we got here, then we had a successful read. Lets begin to
  1601.    ' parse out the boot record information contained in the string DOSBOOT$
  1602.    ' which holds a copy of the boot sector. We will use the MID$ function of
  1603.    ' BASIC to retrieve sub strings out of the Boot record strings DOSBOOT$.
  1604.    ' As follows :
  1605.    '
  1606.    '    SubString = MID$(DOSBOOT$, Offset into boot sector, Bytes to fetch)
  1607.    '
  1608.    'In most DOS applications numbers are converted into characters, then
  1609.    ' written to disk. This is called Binary coded decimal. For example
  1610.    ' dBASE does this also. To convert such a character string back into a
  1611.    ' number, first read the string. Then break it into sub strings of
  1612.    ' one character each. Then, depending on which character is which,
  1613.    ' convert into a number as follows. Leftmost character first, times
  1614.    ' decreasing powers of 16, as shown below:
  1615.    '
  1616.    '   stringtoconvert$ = "ABCD"
  1617.    '
  1618.    '   byte1$ = MID$(stringtoconvert$, 1, 1)
  1619.    '   byte2$ = MID$(stringtoconvert$, 2, 1)
  1620.    '   byte3$ = MID$(stringtoconvert$, 3, 1)
  1621.    '   byte4$ = MID$(stringtoconvert$, 4, 1)
  1622.    '
  1623.    '   byte4 = ASCII value of byte4$ * 4,294,967,296
  1624.    '     or
  1625.    '   byte4& = ASC(byte4$) * 4,294,967,296
  1626.    '
  1627.    '   byte3 = ASCII value of byte3$ * 65,536
  1628.    '   byte2 = ASCII value of byte2$ * 256
  1629.    '   byte1 = ASCII value of byte1$ * 1
  1630.    '
  1631.    ' then add them up:
  1632.    '
  1633.    '   value& = byte4& + byte3& + byte2& + byte1&
  1634.    '
  1635.    '---------------------------------------------------------------
  1636.  
  1637.    'Here we start actually reading out the values of the BPB into
  1638.    ' the BPB type array
  1639.  
  1640.    'get oem info...
  1641.    BPB.OEM = MID$(DOSBOOT$, 4, 8)
  1642.  
  1643.    'figure bytes/sector.
  1644.    Work$ = MID$(DOSBOOT$, 12, 2)        'get sub string
  1645.    Byte1& = ASC(LEFT$(Work$, 1))        'convert each character into ASCII
  1646.    Byte2& = ASC(RIGHT$(Work$, 1))       '  "       "     "        "   "
  1647.    BPB.BytesPerSector = (Byte2& * 256) + Byte1&  'convert into number
  1648.  
  1649.    'figure sectors/fat cell (allocation unit)
  1650.    Work$ = MID$(DOSBOOT$, 14, 1)
  1651.    Byte1& = ASC(LEFT$(Work$, 1))
  1652.    BPB.SectorsPerCluster = Byte1&
  1653.  
  1654.    'figure reserved sectors
  1655.    Work$ = MID$(DOSBOOT$, 15, 2)
  1656.    Byte1& = ASC(LEFT$(Work$, 1))
  1657.    Byte2& = ASC(RIGHT$(Work$, 1))
  1658.    BPB.ReservedSectors = (Byte2& * 256) + Byte1& - 1
  1659.    'the above subtraction of 1 from the last line is an offset for a DOS
  1660.    ' oddity because BPB.ReservedSectors also shows the boot sector itself
  1661.  
  1662.    'figure number of fats
  1663.    Work$ = MID$(DOSBOOT$, 17, 1)
  1664.    Byte1& = ASC(LEFT$(Work$, 1))
  1665.    BPB.NumberOfFats = Byte1&
  1666.  
  1667.    'figure maximum root directory entries
  1668.    Work$ = MID$(DOSBOOT$, 18, 2)
  1669.    Byte1& = ASC(LEFT$(Work$, 1))
  1670.    Byte2& = ASC(RIGHT$(Work$, 1))
  1671.    BPB.RootEntries = (Byte2& * 256) + Byte1&
  1672.    'figure number of sectors
  1673.    Work$ = MID$(DOSBOOT$, 20, 2)
  1674.    Byte1& = ASC(LEFT$(Work$, 1))
  1675.    Byte2& = ASC(RIGHT$(Work$, 1))
  1676.    BPB.Sectors& = (Byte2& * 256) + Byte1&
  1677.    'figure media type where it is stored in HEX format, so first we
  1678.    ' read out its bytes, convert to a number, then into HEX
  1679.    Work$ = MID$(DOSBOOT$, 22, 1)
  1680.    MediaType$ = HEX$(ASC(LEFT$(Work$, 1)))
  1681.    Base$ = "[" + HEX$(ASC(LEFT$(Work$, 1))) + "H]"
  1682.  
  1683.    SELECT CASE MediaType$
  1684.      CASE "F8"
  1685.       MediaDesc$ = " hard disk drive"
  1686.      CASE ELSE
  1687.       MediaDesc$ = " floppy disk drive"
  1688.    END SELECT
  1689.  
  1690.    BPB.MediaType = Base$ + MediaDesc$
  1691.  
  1692.    'figure number of sectors/fat
  1693.    Work$ = MID$(DOSBOOT$, 23, 2)
  1694.    Byte1& = ASC(LEFT$(Work$, 1))
  1695.    Byte2& = ASC(RIGHT$(Work$, 1))
  1696.    BPB.SectorsPerFAT = (Byte2& * 256) + Byte1&
  1697.  
  1698.    'figure clusters
  1699.    BPB.Clusters = BPB.Sectors& \ BPB.SectorsPerCluster
  1700.  
  1701.    'figure sectors/disk track
  1702.    Work$ = MID$(DOSBOOT$, 25, 2)
  1703.    Byte1& = ASC(LEFT$(Work$, 1))
  1704.    Byte2& = ASC(RIGHT$(Work$, 1))
  1705.    BPB.SectorsPerTrack = (Byte2& * 256) + Byte1&
  1706.  
  1707.    'figure number of disk heads
  1708.    Work$ = MID$(DOSBOOT$, 27, 2)
  1709.    Byte1& = ASC(LEFT$(Work$, 1))
  1710.    Byte2& = ASC(RIGHT$(Work$, 1))
  1711.    BPB.NumberOfHeads = (Byte2& * 256) + Byte1&
  1712.  
  1713.    'figure number (if any) of hidden sectors
  1714.    Work$ = MID$(DOSBOOT$, 29, 2)
  1715.    Byte1& = ASC(LEFT$(Work$, 1))
  1716.    Byte2& = ASC(RIGHT$(Work$, 1))
  1717.    BPB.NumberHiddenSectors = (Byte2& * 256) + Byte1&
  1718.  
  1719.    '------------------get FAT start address-------------------
  1720.    'the FAT starts right after (boot_sector + reserved_sectors)
  1721.    'so start of FAT is...0 + BPB.ReservedSectors + 1
  1722.    BPB.StartOFFAT = BPB.ReservedSectors + 1     '+1 to offset -1 from DOS
  1723.                                                 ' reserved sectors count
  1724.  
  1725.    '------------------get root start address-------------------
  1726.    'After the boot sector and the FAT comes the root directory
  1727.    'Add 1 to account for the boot sector itself
  1728.    BPB.StartOFRoot = 1 + (BPB.NumberOfFats * BPB.SectorsPerFAT)
  1729.    '------------------get disk data start address-------------------
  1730.    'After the boot sector, the FAT and the root directory, starts
  1731.    'the actual disk space that we can use...
  1732.  
  1733.    BootSector = 1
  1734.    StartOfData = BootSector + (BPB.NumberOfFats * BPB.SectorsPerFAT)
  1735.    StartOfData = StartOfData + ((BPB.RootEntries * 32) \ BPB.BytesPerSector)
  1736.  
  1737.    BPB.StartOfData = StartOfData
  1738.  
  1739.    '------------------end of routine -------------------
  1740.    'if we got here then we had no errors so set error flag Status
  1741.    ' to show no errors. The Type array BPB now holds all the DOS
  1742.    ' boot sector information for display
  1743.    '
  1744.    Status = 0   'we made with no errors, set error status to 0 or none
  1745.  
  1746. END SUB
  1747.  
  1748. END ADVANCED BASIC ADVANCED BASIC
  1749. THE BOOK OF THE MONTH THE BOOK OF THE MONTH
  1750. THE BOOK OF THE MONTH THE BOOK OF THE MONTH THE BOOK OF THE MONTH THE BOOK OF
  1751.  
  1752. In this segment we review a book that has to do with programing. For November
  1753. the book reviewed is ADVANCED MSDOS The Microsoft guide for Assembly Language
  1754. and C programmers.
  1755.  
  1756. Author    : Ray Duncan
  1757. Publisher : Microsoft Press
  1758. Dated     : 1986
  1759. Cost      : $22.95
  1760. Available : this copy bought at Software Etc.,
  1761.  
  1762. I needed to understand how DOS actually operates to write a program utility.
  1763. I write in BASIC PDS because I like the flexibility, so I went forth into
  1764. the book world seeking an in-depth DOS book featuring BASIC. And guess what?
  1765. It seems that no such book exists. In desperation and despondence I bought
  1766. Advanced MS DOS, hoping to glean enough about DOS to piece together my own
  1767. code.
  1768.  
  1769. Well, I was successful. The book is well endowed with examples, as the name
  1770. would suggest though, they are almost all in assembly language. Luckily
  1771. for me these examples are fully commented and explained in the text.
  1772. Once upon a time when dinosaurs called the 6502 roamed the earth, and was
  1773. king of it, I had played with assembler. But I am by no means expert in it.
  1774. What I got was a good introduction to DOS and assembly language. Now this
  1775. isn't bad, but it is not what I wanted.
  1776. The author constantly refers to CP/M, as if we all already knew all about
  1777. CP/M and wrote in it everyday. I guess in fairness the book was written
  1778. for assembly language programers. Many of whom I guess used to write in
  1779. CP/M. (Control Program Microcode, the predecessor of DOS written by Digital
  1780. Research, the same folks who give us GEM Desktop and other programs.)
  1781. Unfortunately, I never did write in CP/M so all of the examples relating to
  1782. CP/M were useless to me! What I did learn from this study was that DOS is
  1783. quite a bit a kludge of past systems. Operations and functions are often
  1784. carried out in several ways to make them CP/M 'ish or "compatible" for
  1785. re-writing code. I guess I now know what they say DOS is showing it's age.
  1786. For the stout of heart and those interested in the evolution of DOS this is
  1787. good reading.
  1788.  
  1789. But to get back to my application for a minute. The books does explain
  1790. well such concepts as directory entries, file management and memory
  1791. management. Although I found myself reading and re-reading the "simplified"
  1792. block structure of several functions. The real benefit to this book to me
  1793. though is its complete indexing of all DOS functions, with examples and
  1794. explanations. I found this to be of most value. Often, the examples in the
  1795. book under each heading, while written for assembly language, are directly
  1796. useable in BASIC using the Call Interrupt routine. At last, I had found my
  1797. answer - I just couldn't figure it out without reading it 20 times!
  1798. But, I did ultimately figure it out, and that's the point.
  1799.  
  1800. In any event, the books is easier to read, more informative and holds more
  1801. examples than the DOS Technical Reference from IBM. In summary, I found the
  1802. book hard to read, it assumed the reader knew too much. The examples often
  1803. said things like ..."use the directory entry to find..." without
  1804. indicating how one might go about finding the directory entry and then
  1805. processing it. This is where the reading of each section 20 times comes in.
  1806.  
  1807. I did find the book interesting with is genealogy of DOS and very thorough
  1808. explanation of exactly how DOS does anything it does. Also, as stated
  1809. earlier, the complete indexing of functions and DOS calls in invaluable.
  1810. As a side note, going through the amply commented examples given in assembly
  1811. language you get a good understanding of how assembly language operates.
  1812. And that is a valuable thing in itself.
  1813.  
  1814. I recommend this book to anyone who writes in BASIC and is trying to get
  1815. the most from DOS. A must for any programming using the CALL Interrupt or
  1816. InterruptX routines or a BASIC programmer writing your own DOS functions.
  1817.  
  1818. Hank Marquis
  1819. END THE BOOK OF THE MONTH THE BOOK OF THE MONTH
  1820. SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH
  1821. SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH SOFTWARE
  1822.  
  1823. In this segment, we review a software program, utility or add-on for BASIC.
  1824. This month, we take a look at the A.J.S. Publishing package db/LIB. An add
  1825. on library for letting BASIC programmers read, write, modify, create and
  1826. use dBASE III and dBASE III+ style databases and indexes.
  1827.  
  1828. Publisher      : A.J.S. Publishing
  1829. Version tested : 2.0
  1830. Dated          : 1990
  1831. Cost           : $145.00 ($495.00 LAN version)
  1832. Available      : Contact A.J.S. publishing at:
  1833.                  A.J.S. Publishing, Inc.
  1834.                  P.O. Box 83220
  1835.                  Los Angeles, CA 90083
  1836.                  phone : 213.215.9145
  1837.  
  1838. What is it? db/LIB is a set of routines which you include into programs
  1839. you write. These sub routines let you do any virtually any database
  1840. management function. You can read and write dBASE files, create dBASE files
  1841. and indexes and an entire host of other database file and index functions.
  1842.  
  1843. These routines are all written in assembly language and they are fast. I
  1844. compared a program written using dBASE and one written in BASIC. On every
  1845. account, the BASIC program was faster than dBASE itself! db/LIB indexed
  1846. faster, searched faster and also resulted in a smaller program.
  1847.  
  1848. Included with the package are 12 already written BASIC programs. Each
  1849. amply demonstrating some needed database function. You are free to 'cut and
  1850. paste' from these demos into your own applications. That is a real time
  1851. saver. Also built into db/LIB is a full featured expression evaluator.
  1852. Expressions make selecting data from a database easier. You build a string
  1853. using BASIC syntax to select a record or record. For example, "EQUIPMENT
  1854. .EQ. 'CODEX'" Then pass this to the expression evaluator. A routine then
  1855. creates a macro, which you use on subsequent calls to check against each
  1856. record. If the record matches the macro, the expression evaluator returns
  1857. a "T", if is does not, it returns a "F". And it too is really fast. I had
  1858. never used one before, and now I wonder how I ever got by without one.
  1859.  
  1860. The book offers decent information, although at times the examples and
  1861. lacking in depth and the writing is a bit 'high'. That is you read the
  1862. calls two or three times until you understand it. My biggest complaint
  1863. is not with the program - it is with the book. I used db/LIB for a
  1864. recent database project (written, by the way, in QuickBASIC 4.5). As
  1865. I was using the book everyday, it began to fall apart. I spoke with a
  1866. printer friend who told me that the type of binding used on the db/LIB
  1867. book was prone to falling apart. So far I haven't lost any pages, but
  1868. the book is in tatters.
  1869.  
  1870. The technical support from A.J.S. is fine. I never was put on hold, although
  1871. once in a while I had to re-dial several times. But I always got though
  1872. and got clear answers to my questions.
  1873.  
  1874. From a technical note, I tried using db/LIB for another project. This one
  1875. in the PDS. It turns out that db/LIB is not compatible with the PDS use of
  1876. far strings. A.J.S. says they are working on an upgrade. That's not too bad
  1877. as many add-on library vendors have to upgrade their product for far
  1878. strings. Relatedly, I then tried using db/LIB with QB.45, to build
  1879. a custom runtime module. The application had about 12 executable modules,
  1880. each needing part or all of the A.J.S. library. A perfect application for
  1881. a custom BASIC runtime module if I ever saw one. After the module was
  1882. compiled, the application seized up every time. During an investigation I
  1883. found out the assembly language calls (which is what db/LIB is written in)
  1884. must be true FAR CALLS. One phone call to A.J.S. cleared it up. The db/LIB
  1885. current package does not use FAR CALLS. The package for the PDS will. So I
  1886. guess if you want to use a custom runtime module, you won't be using db/LIB.
  1887. In summary, db/LIB lets BASIC programmers access the world of real database
  1888. management. It is a no nonsense, fast, efficient and economical package.
  1889. It's bevy of pre-written programs, each well written and commented are a
  1890. real plus. The expression evaluator is a real gift - once you realize the
  1891. power it offers, you find yourself using everywhere. If you are a BASIC
  1892. programmer, writing in QuickBASIC and need to read/write dBASE files then
  1893. db/LIB is for you. If you are writing programs and want to use a file
  1894. structure that is an industry standard, then db/LIB is the choice. Even
  1895. though I am sitting on the edge of my seat waiting for the far strings
  1896. upgrade, I still highly recommend db/LIB from A.J.S. Publishing.
  1897.  
  1898.  
  1899. END SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH
  1900.